Do: almost finished. Just make a couple of these. See how to hide code chunks for big ones of groups at end. See if there is a simple markdown solution to seeing dfsummary or remove the histograms.

Campus-Level Data for 2019:

This example highlights my data wrangling and visualization skills in an exploratory analysis of racial/ethnic inequity in public schools in Texas. This was a pet project that I created to satisfy my own curiosity on the topic and to get to know the publicly available data related to education in Texas. I clean, validate, and join several data sets with the end goal of exploring the relationship between race/ethnicity, economic disparity, and educational outcomes in Texas public schools. This page provides the steps required to go from several raw public data sets to produce the following interactive visualization, as well as several others, using the R packages listed below:

final example here using full code in echo = F chunk.

Load Packages

library(tidyverse)
library(readxl)
library(plotly)
library(gapminder)
library(kableExtra)
library(summarytools)

Percent of Students Meeting Grade-Level by Race/Ethnic Categories

For educational outcomes, I use the publicly available Texas Academic Performance Report (TAPR) data for the year 2019. This is a very large data set that that I have subsetted to focus on the percentage of students in three racial/ethnic categories (variable: Student Group) who meet the expectations of their respective grade levels (variable: meets_level_pct). I also include the numeric campus identifier cid and the respective denominators for each percentile value (variable: meets_denom), which informs the absolute number of students from each category at the school. I use this value to weight the observations in the Loess smoothing function and to inform the size parameter in the plots. For the original variable names, see the TAPR Codebook.

As described in the codebook for the data set, student groups’ percentile values with very small denominators are masked (coded as negative placeholder values of “-1”) in order to preclude the possibility of identification of individuals in the data. I exclude these masked values from the data set, as well as any school that reports missing data for all three student groups. Denominators of the second smallest student group for each school are also masked with a unique placeholder (“-3”) when the smallest group requires masking. I replace this set of masked values with a reasonable proxy of half the size of the next largest group in the data.

This code does the following:

  1. Read in subsetted TAPR Data
  2. Filter schools with all missing values
  3. Pivot the data to include (up to) three observations per school, corresponding to the three percentile scores meets_level_pct of students in each racial/ethnic category that meet the grade-level expectations.
  4. Create category names for student groups: Student Group and group_denom
  5. Remove masked data for percentiles and recode the masked denominator with the proxy.
  6. Create an additional variable ln_meets_denom of logged group size for use as weight and size in the plots.

The result is

campus_2019 <- read.csv("docs/TAPR_2019_subset.csv") %>%
  transmute(
    cid = CAMPUS,
    `African American` = CDB00A001219R,
    `Hispanic` = CDH00A001219R,
    `White` = CDW00A001219R,
    aa_total = CDB00A001019D,
    h_total = CDH00A001019D,
    w_total = CDW00A001019D) %>%
  # Filter out schools with no data (~ 10% of campuses)
  filter(!is.na(`African American`) | !is.na(`Hispanic`) | !is.na(`White`))

data <- cbind(
  pivot_longer(campus_2019 %>% select(1:4), cols = 2:4, names_to = "Student Group", values_to = "Meets Grade Level (%)"),
  pivot_longer(campus_2019 %>% select(5:7), cols = 1:3, names_to = "group_denom", values_to = "Size of Student Group at School")) %>%
  group_by(cid) %>%
  mutate(
    `Meets Grade Level (%)` = ifelse(`Meets Grade Level (%)` == -1, NA, `Meets Grade Level (%)`), # rate = -1 is masked data and unusable
    `Size of Student Group at School` = ifelse(`Size of Student Group at School` == -1, NA, # denominator = -1 is masked data and unusable
                     `Size of Student Group at School`), 
    `Size of Student Group at School` = ifelse(`Size of Student Group at School` == -3, # denominator = -3 is second smallest group
                     nth(`Size of Student Group at School`, 2, order_by = `Size of Student Group at School`) / 2, 
                     `Size of Student Group at School`), # recover with reasonable proxy of half largest group
    ln_meets_denom = log(`Size of Student Group at School`)) %>% # log will be better for size parameter
  ungroup()


data[1:9,] %>%
  kbl(caption = "Structure of the Data Set: Showing the First Three Schools") %>%
  kable_paper("hover", full_width = F)
Structure of the Data Set: Showing the First Three Schools
cid Student Group Meets Grade Level (%) group_denom Size of Student Group at School ln_meets_denom
1902001 African American 71 aa_total 7 1.945910
1902001 Hispanic 58 h_total 19 2.944439
1902001 White 67 w_total 180 5.192957
1902041 African American 0 aa_total 5 1.609438
1902041 Hispanic 48 h_total 31 3.433987
1902041 White 60 w_total 282 5.641907
1902103 African American 50 aa_total 6 1.791759
1902103 Hispanic 75 h_total 12 2.484907
1902103 White 62 w_total 330 5.799093

Campus-Level Poverty Data

For campus-level poverty statistics, I use the public data available at the Texas Education Agency’s (TEA) school data visualizer, which includes an extensive set of campus-level attributes. I am primarily interested in the percentage of students from economically disadvantaged backgrounds, measured as the percentage of students “…Students eligible for free or reduced-price lunch or other public assistance as reported on the PEIMS October snapshot” (see definitions). Unfortunately, the source does not reveal precisely which year this data comes from! School-level poverty is a relatively stable attribute, though, so this is not a huge concern.

The challenge is that this data does not include the campus ID as a stand-alone variable. I must first extract it from a longer string to create a comparable cid variable that can be used to join this data set to the first one. Because there is no universal pattern to parse this string, I have to split on three different patterns and extract the rightmost element in order to recover all the campus identifiers. This is because some schools have more than one set of the ” (” pattern on the right-hand side of the “||” pattern. Each

school <- read_excel("docs/school.xlsx")
school <- school %>%
  transmute(
    Campus = `Campus or District`, 
    `Eco. Disadvantaged (%)` = `% Eco Disadvantaged`,
    `Campus Type` = factor(`Entity Type`),
    enrollment_public = Enrollment) %>%
  mutate(
    n = str_split(Campus, " \\|\\|", simplify = TRUE)[,2], # First split on "||"
    nn = str_split(n, " \\(", simplify = TRUE)[,2], # Next splot on " ("
    nnn = str_split(n, " \\(", simplify = TRUE)[,3], # A few have an additional " ("
    cid = as.numeric(gsub("\\D+", "", nn)),
    cid = ifelse(is.na(cid), as.numeric(gsub("\\D+", "", nnn)), cid)) %>%
  select(-starts_with("n"))

school[1:5,] %>%
  kbl(caption = "Structure of the Poverty Data after Recovering the Campus Identifier") %>%
  kable_paper("hover", full_width = F)
Structure of the Poverty Data after Recovering the Campus Identifier
Campus Eco. Disadvantaged (%) Campus Type enrollment_public cid
CAYUGA H S || CAYUGA ISD (001902001) 34.9 High School 166 1902001
CAYUGA MIDDLE || CAYUGA ISD (001902041) 40.6 Middle & Jr. High School 133 1902041
CAYUGA EL || CAYUGA ISD (001902103) 38.1 Elementary School 236 1902103
ELKHART H S || ELKHART ISD (001903001) 47.4 High School 344 1903001
ELKHART MIDDLE || ELKHART ISD (001903041) 45.1 Middle & Jr. High School 268 1903041

Codes: School, District, Region, County

This data set from TEA includes the codes that can be used for geographic identification in the plots.

codes <- read_csv("docs/school and district.csv")
codes <- codes %>%
  transmute(
    cid = as.numeric(gsub("[^[:alnum:] ]", "", `School Number`)),
    district = as.numeric(gsub("[^[:alnum:] ]", "", `District Number`)),
    county = as.numeric(gsub("[^[:alnum:] ]", "", `County Number`)),
    region = as.numeric(gsub("[^[:alnum:] ]", "", `ESC Region Served`)))

Join Data and See Descriptive Statistics

Taking the TAPR 2019 data as the base set, I match approximately 98.3% of campuses with the TEA school-level data by their campus id (cid). For parsimony, I exclude those %1.7% campuses that do not exist in both data sets. We can see that the masking and missing data results in approximately 10% of missing data from the TAPR 2019 data; however, due to the logic of how the masking is coded, those masked observations are likely to be smaller, relatively more homogeneous schools, otherwise, they would not have very small absolute numbers of students from any of the three racial/ethnic backgrounds.

#campus <- left_join(data, school, by = "cid") %>% left_join(codes, by = "cid")
campus <- left_join(school, codes, by = "cid") %>% right_join(data, by = "cid") %>%
  filter(!is.na(Campus))
dfSummary(campus,
          plain.ascii  = FALSE, 
          style        = "grid", 
          graph.magnif = 0.75, 
          valid.col    = FALSE,
          tmp.img.dir  = "/tmp",
          silent = TRUE)

Data Frame Summary

Dimensions: 23658 x 13
Duplicates: 0

No Variable Stats / Values Freqs (% of Valid) Graph Missing
1 Campus
[character]
1. 3D ACADEMY || DONNA ISD (
2. A & M CONS H S || COLLEGE
3. A & M CONSOLIDATED MIDDLE
4. A B DUNCAN COLLEGIATE EL
5. A C BLUNT MIDDLE || ARANS
6. A C JONES H S || BEEVILLE
7. A C JONES HEALTH PROFESSI
8. A E BUTLER INT || QUINLAN
9. A G ELDER EL || JOSHUA IS
10. A LEAL JR MIDDLE || HARLA
[ 7876 others ]
3 ( 0.0%)
3 ( 0.0%)
3 ( 0.0%)
3 ( 0.0%)
3 ( 0.0%)
3 ( 0.0%)
3 ( 0.0%)
3 ( 0.0%)
3 ( 0.0%)
3 ( 0.0%)
23628 (99.9%)
0
(0.0%)
2 Eco. Disadvantaged (%)
[numeric]
Mean (sd) : 63.5 (26.1)
min < med < max:
0 < 67.5 < 100
IQR (CV) : 42.1 (0.4)
982 distinct values 0
(0.0%)
3 Campus Type
[factor]
1. Elementary School
2. High School
3. K-12 Schools
4. Middle & Jr. High School
12963 (54.8%)
4539 (19.2%)
1284 ( 5.4%)
4872 (20.6%)
0
(0.0%)
4 enrollment_public
[numeric]
Mean (sd) : 646.5 (538.7)
min < med < max:
1 < 526.5 < 5328
IQR (CV) : 395 (0.8)
1728 distinct values 0
(0.0%)
5 cid
[numeric]
Mean (sd) : 115822704 (72639619)
min < med < max:
1902001 < 101912278 < 254902101
IQR (CV) : 120998818 (0.6)
7886 distinct values 0
(0.0%)
6 district
[numeric]
Mean (sd) : 115857 (72676.9)
min < med < max:
1902 < 101912 < 254902
IQR (CV) : 120999 (0.6)
1193 distinct values 144
(0.6%)
7 county
[numeric]
Mean (sd) : 115 (72.7)
min < med < max:
1 < 101 < 254
IQR (CV) : 121 (0.6)
253 distinct values 144
(0.6%)
8 region
[numeric]
Mean (sd) : 9.7 (5.7)
min < med < max:
1 < 10 < 20
IQR (CV) : 9 (0.6)
20 distinct values 144
(0.6%)
9 Student Group
[character]
1. African American
2. Hispanic
3. White
7886 (33.3%)
7886 (33.3%)
7886 (33.3%)
0
(0.0%)
10 Meets Grade Level (%)
[integer]
Mean (sd) : 46.2 (18.7)
min < med < max:
0 < 45 < 100
IQR (CV) : 25 (0.4)
100 distinct values 2392
(10.1%)
11 group_denom
[character]
1. aa_total
2. h_total
3. w_total
7886 (33.3%)
7886 (33.3%)
7886 (33.3%)
0
(0.0%)
12 Size of Student Group at School
[numeric]
Mean (sd) : 376.6 (513.2)
min < med < max:
2.5 < 195 < 5395
IQR (CV) : 411 (1.4)
2329 distinct values 2539
(10.7%)
13 ln_meets_denom
[numeric]
Mean (sd) : 5.1 (1.4)
min < med < max:
0.9 < 5.3 < 8.6
IQR (CV) : 2 (0.3)
2329 distinct values 2539
(10.7%)

Exploring the Relationship between Race/Ethnicity, Poverty, and Education Outcomes

Here I write a function to build plotly interactive visualizations (hover over the image to get point-level data). School-level data is plotted on the x-axis based on poverty levels Eco. Disadvanted (%). School-level data disaggregated by race/ethnicity on the y-axis to show each racial/ethnic groups’s academic performance as Meets Grade Level (%). I write a custom function that takes the data and two filtering parameters to slice the full data set: campus %>% filter('Campus Type' == {{type}} & region == {{r}}). I pass this function to lapply() to generate any or all combinations of region and Campus Type (up to )

r <- 1:20

plots <- function(data, r, type) {

t <-  ggplot(data = campus %>% filter(`Campus Type` == {{type}} & region == {{r}}), 
         aes(label = `Campus`, label2 = `Size of Student Group at School`, x = `Eco. Disadvantaged (%)`, y = `Meets Grade Level (%)`)) +
  geom_line(alpha = .3, aes(group = cid)) +
  geom_point(alpha = .4, aes(size = `Size of Student Group at School`, color = `Student Group`)) +
  geom_smooth(size = 2, method = loess, color = "black", se = FALSE, aes(group = `Student Group`)) +
  geom_smooth(size = 1.8, method = loess, se = FALSE, aes(color = `Student Group`)) +
  guides(size = FALSE) +
  theme(legend.position = "bottom") +
  xlim(c(0,100)) + ylim(c(0,100)) + 
    ggtitle(paste("Region", {{r}}, {{type}}, "Performance by Race/Ethnicity and Poverty", sep = " "))
  
t[[r]] <- ggplotly(t, tooltip = c("label", "label2", "x", "y"))

#print(t[[r]])
#return(t[[r]])
}

# This works in R but not for markdown to html
# for (i in 1:20){
#   plots(campus, i)
# }

gg_hs <- lapply(r, plots, data = campus, type = "High School")

gg_es <- lapply(r, plots, data = campus, type = "Elementary School")
gg_es

[[1]]

[[2]]

[[3]]

[[4]]

[[5]]

[[6]]

[[7]]

[[8]]

[[9]]

[[10]]

[[11]]

[[12]]

[[13]]

[[14]]

[[15]]

[[16]]

[[17]]

[[18]]

[[19]]

[[20]]

gg_hs[[1]]
subplot(gg_hs[[10]], gg_es[[10]], gg_hs[[20]], gg_es[[20]], nrows = 2)
htmltools::tagList(list(gg_hs[[10]], gg_es[[10]], gg_hs[[20]], gg_es[[20]]))
htmltools::tagList(list(gg_hs[[1]], gg_hs[[3]], gg_es[[1]], gg_es[[3]]))

Private Schools Data 2018 - 2019

private <-  read_excel("docs/private schools 2018-2019.xlsx")
private <-private %>%
  filter(!`Grade High` %in% c("Pre-K", "Early Education") & Closed == FALSE) %>%
  transmute(
    district = as.numeric(`District Number`),
    county = as.numeric(`County Number`),
    region = as.numeric(`Region Name`),
    enrollment_private = as.numeric(Enrollment))

private_region <- private %>%
  group_by(region) %>%
  summarise(
    private_enrollment_region = sum(enrollment_private, na.rm = TRUE)) %>%
  ungroup()

public_region <- school %>%
  left_join(codes, by = "cid") %>%
  group_by(region) %>%
  summarise(
    public_enrollment_region = sum(enrollment_public, na.rm = TRUE)) %>%
  ungroup()
  
region <- left_join(private_region, public_region, by = "region") 
region <- region %>%
  mutate(
    `Total Enrollment` = private_enrollment_region + public_enrollment_region,
    `Private Students in Region (%)` = 100 * ( private_enrollment_region / `Total Enrollment`),
    Region_lab = paste("Region", region, sep = " "),
    Region = factor(region, ordered = TRUE, labels = Region_lab)) %>%
  arrange(desc(`Private Students in Region (%)`)) %>%
  mutate(
    Region_lab = paste("Region", region, sep = " "),
    `Region Ordered` = factor(region, levels = region, labels = paste("Region", region, sep = " ")))

#rm(private, private_region, public_region)

ggplotly(ggplot(region, aes(x = log(`Total Enrollment`), y = `Private Students in Region (%)`, label = region)) +
  geom_smooth(alpha = .4, method = lm) +
    geom_text(size = 5))
## `geom_smooth()` using formula 'y ~ x'
ggplotly(ggplot(region %>% filter(!region %in% c(1, 3)), aes(x = log(`Total Enrollment`), y = `Private Students in Region (%)`, label = region)) +
  geom_smooth(alpha = .4, method = lm) +
    geom_text(size = 5))
## `geom_smooth()` using formula 'y ~ x'
p1 <- ggplotly(ggplot(region, aes(x = `Private Students in Region (%)`, y = Region)) +
  geom_col())

p2 <- ggplotly(ggplot(region, aes(x = `Private Students in Region (%)`, y = `Region Ordered`)) +
  geom_col())

subplot(p1, p2, margin = 0.07)
LS0tDQp0aXRsZTogIkV4LiAxIFRleGFzIFB1YmxpYyBTY2hvb2xzIg0KDQotLS0NCg0KRG86IGFsbW9zdCBmaW5pc2hlZC4gSnVzdCBtYWtlIGEgY291cGxlIG9mIHRoZXNlLiBTZWUgaG93IHRvIGhpZGUgY29kZSBjaHVua3MgZm9yIGJpZyBvbmVzIG9mIGdyb3VwcyBhdCBlbmQuIFNlZSBpZiB0aGVyZSBpcyBhIHNpbXBsZSBtYXJrZG93biBzb2x1dGlvbiB0byBzZWVpbmcgZGZzdW1tYXJ5IG9yIHJlbW92ZSB0aGUgaGlzdG9ncmFtcy4NCg0KIyBDYW1wdXMtTGV2ZWwgRGF0YSBmb3IgMjAxOTogDQoNClRoaXMgZXhhbXBsZSBoaWdobGlnaHRzIG15IGRhdGEgd3JhbmdsaW5nIGFuZCB2aXN1YWxpemF0aW9uIHNraWxscyBpbiBhbiBleHBsb3JhdG9yeSBhbmFseXNpcyBvZiByYWNpYWwvZXRobmljIGluZXF1aXR5IGluIHB1YmxpYyBzY2hvb2xzIGluIFRleGFzLiBUaGlzIHdhcyBhIHBldCBwcm9qZWN0IHRoYXQgSSBjcmVhdGVkIHRvIHNhdGlzZnkgbXkgb3duIGN1cmlvc2l0eSBvbiB0aGUgdG9waWMgYW5kIHRvIGdldCB0byBrbm93IHRoZSBwdWJsaWNseSBhdmFpbGFibGUgZGF0YSByZWxhdGVkIHRvIGVkdWNhdGlvbiBpbiBUZXhhcy4gSSBjbGVhbiwgdmFsaWRhdGUsIGFuZCBqb2luIHNldmVyYWwgZGF0YSBzZXRzIHdpdGggdGhlIGVuZCBnb2FsIG9mIGV4cGxvcmluZyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gcmFjZS9ldGhuaWNpdHksIGVjb25vbWljIGRpc3Bhcml0eSwgYW5kIGVkdWNhdGlvbmFsIG91dGNvbWVzIGluIFRleGFzIHB1YmxpYyBzY2hvb2xzLiBUaGlzIHBhZ2UgcHJvdmlkZXMgdGhlIHN0ZXBzIHJlcXVpcmVkIHRvIGdvIGZyb20gc2V2ZXJhbCByYXcgcHVibGljIGRhdGEgc2V0cyB0byBwcm9kdWNlIHRoZSBmb2xsb3dpbmcgaW50ZXJhY3RpdmUgdmlzdWFsaXphdGlvbiwgYXMgd2VsbCBhcyBzZXZlcmFsIG90aGVycywgdXNpbmcgdGhlIFIgcGFja2FnZXMgbGlzdGVkIGJlbG93Og0KDQpmaW5hbCBleGFtcGxlIGhlcmUgdXNpbmcgZnVsbCBjb2RlIGluIGVjaG8gPSBGIGNodW5rLg0KDQojIyMgTG9hZCBQYWNrYWdlcw0KYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHJlYWR4bCkNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShnYXBtaW5kZXIpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpsaWJyYXJ5KHN1bW1hcnl0b29scykNCmBgYA0KDQojIyMgUGVyY2VudCBvZiBTdHVkZW50cyBNZWV0aW5nIEdyYWRlLUxldmVsIGJ5IFJhY2UvRXRobmljIENhdGVnb3JpZXMNCg0KRm9yIGVkdWNhdGlvbmFsIG91dGNvbWVzLCBJIHVzZSB0aGUgcHVibGljbHkgYXZhaWxhYmxlIFtUZXhhcyBBY2FkZW1pYyBQZXJmb3JtYW5jZSBSZXBvcnQgKFRBUFIpIGRhdGFdKGh0dHBzOi8vcnB0c3ZyMS50ZWEudGV4YXMuZ292L3BlcmZyZXBvcnQvdGFwci8yMDE5L2Rvd25sb2FkL0Rvd25sb2FkRGF0YS5odG1sKSBmb3IgdGhlIHllYXIgMjAxOS4gVGhpcyBpcyBhIHZlcnkgbGFyZ2UgZGF0YSBzZXQgdGhhdCB0aGF0IEkgaGF2ZSBzdWJzZXR0ZWQgdG8gZm9jdXMgb24gdGhlIHBlcmNlbnRhZ2Ugb2Ygc3R1ZGVudHMgaW4gdGhyZWUgcmFjaWFsL2V0aG5pYyBjYXRlZ29yaWVzICh2YXJpYWJsZTogYFN0dWRlbnQgR3JvdXBgKSB3aG8gbWVldCB0aGUgZXhwZWN0YXRpb25zIG9mIHRoZWlyIHJlc3BlY3RpdmUgZ3JhZGUgbGV2ZWxzICh2YXJpYWJsZTogYG1lZXRzX2xldmVsX3BjdGApLiBJIGFsc28gaW5jbHVkZSB0aGUgbnVtZXJpYyBjYW1wdXMgaWRlbnRpZmllciBgY2lkYCBhbmQgdGhlIHJlc3BlY3RpdmUgZGVub21pbmF0b3JzIGZvciBlYWNoIHBlcmNlbnRpbGUgdmFsdWUgKHZhcmlhYmxlOiBgbWVldHNfZGVub21gKSwgd2hpY2ggaW5mb3JtcyB0aGUgYWJzb2x1dGUgbnVtYmVyIG9mIHN0dWRlbnRzIGZyb20gZWFjaCBjYXRlZ29yeSBhdCB0aGUgc2Nob29sLiBJIHVzZSB0aGlzIHZhbHVlIHRvIHdlaWdodCB0aGUgb2JzZXJ2YXRpb25zIGluIHRoZSBMb2VzcyBzbW9vdGhpbmcgZnVuY3Rpb24gYW5kIHRvIGluZm9ybSB0aGUgc2l6ZSBwYXJhbWV0ZXIgaW4gdGhlIHBsb3RzLiBGb3IgdGhlIG9yaWdpbmFsIHZhcmlhYmxlIG5hbWVzLCBzZWUgdGhlIFtUQVBSIENvZGVib29rXShodHRwczovL3JwdHN2cjEudGVhLnRleGFzLmdvdi9wZXJmcmVwb3J0L3RhcHIvMjAxOS9kb3dubG9hZC9jYW1wc3RhYXIyYi5odG1sKS4gIA0KDQpBcyBkZXNjcmliZWQgaW4gdGhlIFtjb2RlYm9vayBmb3IgdGhlIGRhdGEgc2V0XShodHRwczovL3JwdHN2cjEudGVhLnRleGFzLmdvdi9wZXJmcmVwb3J0L3RhcHIvMjAxOS9kb3dubG9hZC9jYW1wc3RhYXIyYi5odG1sKSwgc3R1ZGVudCBncm91cHMnIHBlcmNlbnRpbGUgdmFsdWVzIHdpdGggdmVyeSBzbWFsbCBkZW5vbWluYXRvcnMgYXJlIG1hc2tlZCAoY29kZWQgYXMgbmVnYXRpdmUgcGxhY2Vob2xkZXIgdmFsdWVzIG9mICItMSIpIGluIG9yZGVyIHRvIHByZWNsdWRlIHRoZSBwb3NzaWJpbGl0eSBvZiBpZGVudGlmaWNhdGlvbiBvZiBpbmRpdmlkdWFscyBpbiB0aGUgZGF0YS4gSSBleGNsdWRlIHRoZXNlIG1hc2tlZCB2YWx1ZXMgZnJvbSB0aGUgZGF0YSBzZXQsIGFzIHdlbGwgYXMgYW55IHNjaG9vbCB0aGF0IHJlcG9ydHMgbWlzc2luZyBkYXRhIGZvciBhbGwgdGhyZWUgc3R1ZGVudCBncm91cHMuIERlbm9taW5hdG9ycyBvZiB0aGUgc2Vjb25kIHNtYWxsZXN0IHN0dWRlbnQgZ3JvdXAgZm9yIGVhY2ggc2Nob29sIGFyZSBhbHNvIG1hc2tlZCB3aXRoIGEgdW5pcXVlIHBsYWNlaG9sZGVyICgiLTMiKSB3aGVuIHRoZSBzbWFsbGVzdCBncm91cCByZXF1aXJlcyBtYXNraW5nLiBJIHJlcGxhY2UgdGhpcyBzZXQgb2YgbWFza2VkIHZhbHVlcyB3aXRoIGEgcmVhc29uYWJsZSBwcm94eSBvZiBoYWxmIHRoZSBzaXplIG9mIHRoZSBuZXh0IGxhcmdlc3QgZ3JvdXAgaW4gdGhlIGRhdGEuICANCg0KVGhpcyBjb2RlIGRvZXMgdGhlIGZvbGxvd2luZzogIA0KDQoxLiBSZWFkIGluIHN1YnNldHRlZCBUQVBSIERhdGENCjIuIEZpbHRlciBzY2hvb2xzIHdpdGggYWxsIG1pc3NpbmcgdmFsdWVzDQozLiBQaXZvdCB0aGUgZGF0YSB0byBpbmNsdWRlICh1cCB0bykgdGhyZWUgb2JzZXJ2YXRpb25zIHBlciBzY2hvb2wsIGNvcnJlc3BvbmRpbmcgdG8gdGhlIHRocmVlIHBlcmNlbnRpbGUgc2NvcmVzIGBtZWV0c19sZXZlbF9wY3RgIG9mIHN0dWRlbnRzIGluIGVhY2ggcmFjaWFsL2V0aG5pYyBjYXRlZ29yeSB0aGF0IG1lZXQgdGhlIGdyYWRlLWxldmVsIGV4cGVjdGF0aW9ucy4NCjQuIENyZWF0ZSBjYXRlZ29yeSBuYW1lcyBmb3Igc3R1ZGVudCBncm91cHM6IGBTdHVkZW50IEdyb3VwYCBhbmQgYGdyb3VwX2Rlbm9tYA0KNS4gUmVtb3ZlIG1hc2tlZCBkYXRhIGZvciBwZXJjZW50aWxlcyBhbmQgcmVjb2RlIHRoZSBtYXNrZWQgZGVub21pbmF0b3Igd2l0aCB0aGUgcHJveHkuDQo2LiBDcmVhdGUgYW4gYWRkaXRpb25hbCB2YXJpYWJsZSBgbG5fbWVldHNfZGVub21gIG9mIGxvZ2dlZCBncm91cCBzaXplIGZvciB1c2UgYXMgd2VpZ2h0IGFuZCBzaXplIGluIHRoZSBwbG90cy4gIA0KDQpUaGUgcmVzdWx0IGlzIA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmNhbXB1c18yMDE5IDwtIHJlYWQuY3N2KCJkb2NzL1RBUFJfMjAxOV9zdWJzZXQuY3N2IikgJT4lDQogIHRyYW5zbXV0ZSgNCiAgICBjaWQgPSBDQU1QVVMsDQogICAgYEFmcmljYW4gQW1lcmljYW5gID0gQ0RCMDBBMDAxMjE5UiwNCiAgICBgSGlzcGFuaWNgID0gQ0RIMDBBMDAxMjE5UiwNCiAgICBgV2hpdGVgID0gQ0RXMDBBMDAxMjE5UiwNCiAgICBhYV90b3RhbCA9IENEQjAwQTAwMTAxOUQsDQogICAgaF90b3RhbCA9IENESDAwQTAwMTAxOUQsDQogICAgd190b3RhbCA9IENEVzAwQTAwMTAxOUQpICU+JQ0KICAjIEZpbHRlciBvdXQgc2Nob29scyB3aXRoIG5vIGRhdGEgKH4gMTAlIG9mIGNhbXB1c2VzKQ0KICBmaWx0ZXIoIWlzLm5hKGBBZnJpY2FuIEFtZXJpY2FuYCkgfCAhaXMubmEoYEhpc3BhbmljYCkgfCAhaXMubmEoYFdoaXRlYCkpDQoNCmRhdGEgPC0gY2JpbmQoDQogIHBpdm90X2xvbmdlcihjYW1wdXNfMjAxOSAlPiUgc2VsZWN0KDE6NCksIGNvbHMgPSAyOjQsIG5hbWVzX3RvID0gIlN0dWRlbnQgR3JvdXAiLCB2YWx1ZXNfdG8gPSAiTWVldHMgR3JhZGUgTGV2ZWwgKCUpIiksDQogIHBpdm90X2xvbmdlcihjYW1wdXNfMjAxOSAlPiUgc2VsZWN0KDU6NyksIGNvbHMgPSAxOjMsIG5hbWVzX3RvID0gImdyb3VwX2Rlbm9tIiwgdmFsdWVzX3RvID0gIlNpemUgb2YgU3R1ZGVudCBHcm91cCBhdCBTY2hvb2wiKSkgJT4lDQogIGdyb3VwX2J5KGNpZCkgJT4lDQogIG11dGF0ZSgNCiAgICBgTWVldHMgR3JhZGUgTGV2ZWwgKCUpYCA9IGlmZWxzZShgTWVldHMgR3JhZGUgTGV2ZWwgKCUpYCA9PSAtMSwgTkEsIGBNZWV0cyBHcmFkZSBMZXZlbCAoJSlgKSwgIyByYXRlID0gLTEgaXMgbWFza2VkIGRhdGEgYW5kIHVudXNhYmxlDQogICAgYFNpemUgb2YgU3R1ZGVudCBHcm91cCBhdCBTY2hvb2xgID0gaWZlbHNlKGBTaXplIG9mIFN0dWRlbnQgR3JvdXAgYXQgU2Nob29sYCA9PSAtMSwgTkEsICMgZGVub21pbmF0b3IgPSAtMSBpcyBtYXNrZWQgZGF0YSBhbmQgdW51c2FibGUNCiAgICAgICAgICAgICAgICAgICAgIGBTaXplIG9mIFN0dWRlbnQgR3JvdXAgYXQgU2Nob29sYCksIA0KICAgIGBTaXplIG9mIFN0dWRlbnQgR3JvdXAgYXQgU2Nob29sYCA9IGlmZWxzZShgU2l6ZSBvZiBTdHVkZW50IEdyb3VwIGF0IFNjaG9vbGAgPT0gLTMsICMgZGVub21pbmF0b3IgPSAtMyBpcyBzZWNvbmQgc21hbGxlc3QgZ3JvdXANCiAgICAgICAgICAgICAgICAgICAgIG50aChgU2l6ZSBvZiBTdHVkZW50IEdyb3VwIGF0IFNjaG9vbGAsIDIsIG9yZGVyX2J5ID0gYFNpemUgb2YgU3R1ZGVudCBHcm91cCBhdCBTY2hvb2xgKSAvIDIsIA0KICAgICAgICAgICAgICAgICAgICAgYFNpemUgb2YgU3R1ZGVudCBHcm91cCBhdCBTY2hvb2xgKSwgIyByZWNvdmVyIHdpdGggcmVhc29uYWJsZSBwcm94eSBvZiBoYWxmIGxhcmdlc3QgZ3JvdXANCiAgICBsbl9tZWV0c19kZW5vbSA9IGxvZyhgU2l6ZSBvZiBTdHVkZW50IEdyb3VwIGF0IFNjaG9vbGApKSAlPiUgIyBsb2cgd2lsbCBiZSBiZXR0ZXIgZm9yIHNpemUgcGFyYW1ldGVyDQogIHVuZ3JvdXAoKQ0KDQoNCmRhdGFbMTo5LF0gJT4lDQogIGtibChjYXB0aW9uID0gIlN0cnVjdHVyZSBvZiB0aGUgRGF0YSBTZXQ6IFNob3dpbmcgdGhlIEZpcnN0IFRocmVlIFNjaG9vbHMiKSAlPiUNCiAga2FibGVfcGFwZXIoImhvdmVyIiwgZnVsbF93aWR0aCA9IEYpDQoNCiAgICAgICAgICAgICAgIA0KYGBgDQoNCiMjIENhbXB1cy1MZXZlbCBQb3ZlcnR5IERhdGENCg0KRm9yIGNhbXB1cy1sZXZlbCBwb3ZlcnR5IHN0YXRpc3RpY3MsIEkgdXNlIHRoZSBwdWJsaWMgZGF0YSBhdmFpbGFibGUgYXQgdGhlIFRleGFzIEVkdWNhdGlvbiBBZ2VuY3kncyAoVEVBKSBbc2Nob29sIGRhdGEgdmlzdWFsaXplcl0oaHR0cHM6Ly9ycHRzdnIxLnRlYS50ZXhhcy5nb3YvcGVyZnJlcG9ydC9hY2NvdW50L3ZhL3ZhX2NvcnJlbGF0ZS5odG1sKSwgd2hpY2ggaW5jbHVkZXMgYW4gZXh0ZW5zaXZlIHNldCBvZiBjYW1wdXMtbGV2ZWwgYXR0cmlidXRlcy4gSSBhbSBwcmltYXJpbHkgaW50ZXJlc3RlZCBpbiB0aGUgcGVyY2VudGFnZSBvZiBzdHVkZW50cyBmcm9tIGVjb25vbWljYWxseSBkaXNhZHZhbnRhZ2VkIGJhY2tncm91bmRzLCBtZWFzdXJlZCBhcyB0aGUgcGVyY2VudGFnZSBvZiBzdHVkZW50cyAiLi4uU3R1ZGVudHMgZWxpZ2libGUgZm9yIGZyZWUgb3IgcmVkdWNlZC1wcmljZSBsdW5jaCBvciBvdGhlciBwdWJsaWMgYXNzaXN0YW5jZSBhcyByZXBvcnRlZCBvbiB0aGUgUEVJTVMgT2N0b2JlciBzbmFwc2hvdCIgKFtzZWUgZGVmaW5pdGlvbnNdKGh0dHBzOi8vcnB0c3ZyMS50ZWEudGV4YXMuZ292L3BlcmZyZXBvcnQvZmFxc2l0ZS9nbG9zc2FyeS5odG1sKSkuIFVuZm9ydHVuYXRlbHksIHRoZSBzb3VyY2UgZG9lcyBub3QgcmV2ZWFsIHByZWNpc2VseSB3aGljaCB5ZWFyIHRoaXMgZGF0YSBjb21lcyBmcm9tISBTY2hvb2wtbGV2ZWwgcG92ZXJ0eSBpcyBhIHJlbGF0aXZlbHkgc3RhYmxlIGF0dHJpYnV0ZSwgdGhvdWdoLCBzbyB0aGlzIGlzIG5vdCBhIGh1Z2UgY29uY2Vybi4gIA0KDQpUaGUgY2hhbGxlbmdlIGlzIHRoYXQgdGhpcyBkYXRhIGRvZXMgbm90IGluY2x1ZGUgdGhlIGNhbXB1cyBJRCBhcyBhIHN0YW5kLWFsb25lIHZhcmlhYmxlLiBJIG11c3QgZmlyc3QgZXh0cmFjdCBpdCBmcm9tIGEgbG9uZ2VyIHN0cmluZyB0byBjcmVhdGUgYSBjb21wYXJhYmxlIGBjaWRgIHZhcmlhYmxlIHRoYXQgY2FuIGJlIHVzZWQgdG8gam9pbiB0aGlzIGRhdGEgc2V0IHRvIHRoZSBmaXJzdCBvbmUuIEJlY2F1c2UgdGhlcmUgaXMgbm8gdW5pdmVyc2FsIHBhdHRlcm4gdG8gcGFyc2UgdGhpcyBzdHJpbmcsIEkgaGF2ZSB0byBzcGxpdCBvbiB0aHJlZSBkaWZmZXJlbnQgcGF0dGVybnMgYW5kIGV4dHJhY3QgdGhlIHJpZ2h0bW9zdCBlbGVtZW50IGluIG9yZGVyIHRvIHJlY292ZXIgYWxsIHRoZSBjYW1wdXMgaWRlbnRpZmllcnMuIFRoaXMgaXMgYmVjYXVzZSBzb21lIHNjaG9vbHMgaGF2ZSBtb3JlIHRoYW4gb25lIHNldCBvZiB0aGUgIiAoIiBwYXR0ZXJuIG9uIHRoZSByaWdodC1oYW5kIHNpZGUgb2YgdGhlICJ8fCIgcGF0dGVybi4gRWFjaCANCg0KDQpgYGB7cn0NCnNjaG9vbCA8LSByZWFkX2V4Y2VsKCJkb2NzL3NjaG9vbC54bHN4IikNCnNjaG9vbCA8LSBzY2hvb2wgJT4lDQogIHRyYW5zbXV0ZSgNCiAgICBDYW1wdXMgPSBgQ2FtcHVzIG9yIERpc3RyaWN0YCwgDQogICAgYEVjby4gRGlzYWR2YW50YWdlZCAoJSlgID0gYCUgRWNvIERpc2FkdmFudGFnZWRgLA0KICAgIGBDYW1wdXMgVHlwZWAgPSBmYWN0b3IoYEVudGl0eSBUeXBlYCksDQogICAgZW5yb2xsbWVudF9wdWJsaWMgPSBFbnJvbGxtZW50KSAlPiUNCiAgbXV0YXRlKA0KICAgIG4gPSBzdHJfc3BsaXQoQ2FtcHVzLCAiIFxcfFxcfCIsIHNpbXBsaWZ5ID0gVFJVRSlbLDJdLCAjIEZpcnN0IHNwbGl0IG9uICJ8fCINCiAgICBubiA9IHN0cl9zcGxpdChuLCAiIFxcKCIsIHNpbXBsaWZ5ID0gVFJVRSlbLDJdLCAjIE5leHQgc3Bsb3Qgb24gIiAoIg0KICAgIG5ubiA9IHN0cl9zcGxpdChuLCAiIFxcKCIsIHNpbXBsaWZ5ID0gVFJVRSlbLDNdLCAjIEEgZmV3IGhhdmUgYW4gYWRkaXRpb25hbCAiICgiDQogICAgY2lkID0gYXMubnVtZXJpYyhnc3ViKCJcXEQrIiwgIiIsIG5uKSksDQogICAgY2lkID0gaWZlbHNlKGlzLm5hKGNpZCksIGFzLm51bWVyaWMoZ3N1YigiXFxEKyIsICIiLCBubm4pKSwgY2lkKSkgJT4lDQogIHNlbGVjdCgtc3RhcnRzX3dpdGgoIm4iKSkNCg0Kc2Nob29sWzE6NSxdICU+JQ0KICBrYmwoY2FwdGlvbiA9ICJTdHJ1Y3R1cmUgb2YgdGhlIFBvdmVydHkgRGF0YSBhZnRlciBSZWNvdmVyaW5nIHRoZSBDYW1wdXMgSWRlbnRpZmllciIpICU+JQ0KICBrYWJsZV9wYXBlcigiaG92ZXIiLCBmdWxsX3dpZHRoID0gRikNCg0KYGBgDQoNCiMgQ29kZXM6IFNjaG9vbCwgRGlzdHJpY3QsIFJlZ2lvbiwgQ291bnR5DQoNClRoaXMgZGF0YSBzZXQgZnJvbSBURUEgaW5jbHVkZXMgdGhlIGNvZGVzIHRoYXQgY2FuIGJlIHVzZWQgZm9yIGdlb2dyYXBoaWMgaWRlbnRpZmljYXRpb24gaW4gdGhlIHBsb3RzLg0KDQpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQpjb2RlcyA8LSByZWFkX2NzdigiZG9jcy9zY2hvb2wgYW5kIGRpc3RyaWN0LmNzdiIpDQpjb2RlcyA8LSBjb2RlcyAlPiUNCiAgdHJhbnNtdXRlKA0KICAgIGNpZCA9IGFzLm51bWVyaWMoZ3N1YigiW15bOmFsbnVtOl0gXSIsICIiLCBgU2Nob29sIE51bWJlcmApKSwNCiAgICBkaXN0cmljdCA9IGFzLm51bWVyaWMoZ3N1YigiW15bOmFsbnVtOl0gXSIsICIiLCBgRGlzdHJpY3QgTnVtYmVyYCkpLA0KICAgIGNvdW50eSA9IGFzLm51bWVyaWMoZ3N1YigiW15bOmFsbnVtOl0gXSIsICIiLCBgQ291bnR5IE51bWJlcmApKSwNCiAgICByZWdpb24gPSBhcy5udW1lcmljKGdzdWIoIlteWzphbG51bTpdIF0iLCAiIiwgYEVTQyBSZWdpb24gU2VydmVkYCkpKQ0KDQpgYGANCg0KDQoNCiMjIEpvaW4gRGF0YSBhbmQgU2VlIERlc2NyaXB0aXZlIFN0YXRpc3RpY3MNCg0KVGFraW5nIHRoZSBUQVBSIDIwMTkgZGF0YSBhcyB0aGUgYmFzZSBzZXQsIEkgbWF0Y2ggYXBwcm94aW1hdGVseSA5OC4zJSBvZiBjYW1wdXNlcyB3aXRoIHRoZSBURUEgc2Nob29sLWxldmVsIGRhdGEgYnkgdGhlaXIgY2FtcHVzIGlkIChgY2lkYCkuIEZvciBwYXJzaW1vbnksIEkgZXhjbHVkZSB0aG9zZSAlMS43JSBjYW1wdXNlcyB0aGF0IGRvIG5vdCBleGlzdCBpbiBib3RoIGRhdGEgc2V0cy4gV2UgY2FuIHNlZSB0aGF0IHRoZSBtYXNraW5nIGFuZCBtaXNzaW5nIGRhdGEgcmVzdWx0cyBpbiBhcHByb3hpbWF0ZWx5IDEwJSBvZiBtaXNzaW5nIGRhdGEgZnJvbSB0aGUgVEFQUiAyMDE5IGRhdGE7IGhvd2V2ZXIsIGR1ZSB0byB0aGUgbG9naWMgb2YgaG93IHRoZSBtYXNraW5nIGlzIGNvZGVkLCB0aG9zZSBtYXNrZWQgb2JzZXJ2YXRpb25zIGFyZSBsaWtlbHkgdG8gYmUgc21hbGxlciwgcmVsYXRpdmVseSBtb3JlIGhvbW9nZW5lb3VzIHNjaG9vbHMsIG90aGVyd2lzZSwgdGhleSB3b3VsZCBub3QgaGF2ZSB2ZXJ5IHNtYWxsIGFic29sdXRlIG51bWJlcnMgb2Ygc3R1ZGVudHMgZnJvbSBhbnkgb2YgdGhlIHRocmVlIHJhY2lhbC9ldGhuaWMgYmFja2dyb3VuZHMuICANCg0KYGBge3IsIHJlc3VsdHMgPSAiYXNpcyJ9DQojY2FtcHVzIDwtIGxlZnRfam9pbihkYXRhLCBzY2hvb2wsIGJ5ID0gImNpZCIpICU+JSBsZWZ0X2pvaW4oY29kZXMsIGJ5ID0gImNpZCIpDQpjYW1wdXMgPC0gbGVmdF9qb2luKHNjaG9vbCwgY29kZXMsIGJ5ID0gImNpZCIpICU+JSByaWdodF9qb2luKGRhdGEsIGJ5ID0gImNpZCIpICU+JQ0KICBmaWx0ZXIoIWlzLm5hKENhbXB1cykpDQpkZlN1bW1hcnkoY2FtcHVzLA0KICAgICAgICAgIHBsYWluLmFzY2lpICA9IEZBTFNFLCANCiAgICAgICAgICBzdHlsZSAgICAgICAgPSAiZ3JpZCIsIA0KICAgICAgICAgIGdyYXBoLm1hZ25pZiA9IDAuNzUsIA0KICAgICAgICAgIHZhbGlkLmNvbCAgICA9IEZBTFNFLA0KICAgICAgICAgIHRtcC5pbWcuZGlyICA9ICIvdG1wIiwNCiAgICAgICAgICBzaWxlbnQgPSBUUlVFKQ0KYGBgDQoNCg0KIyMgRXhwbG9yaW5nIHRoZSBSZWxhdGlvbnNoaXAgYmV0d2VlbiBSYWNlL0V0aG5pY2l0eSwgUG92ZXJ0eSwgYW5kIEVkdWNhdGlvbiBPdXRjb21lcw0KDQpIZXJlIEkgd3JpdGUgYSBmdW5jdGlvbiB0byBidWlsZCBgcGxvdGx5YCBpbnRlcmFjdGl2ZSB2aXN1YWxpemF0aW9ucyAoaG92ZXIgb3ZlciB0aGUgaW1hZ2UgdG8gZ2V0IHBvaW50LWxldmVsIGRhdGEpLiBTY2hvb2wtbGV2ZWwgZGF0YSBpcyBwbG90dGVkIG9uIHRoZSB4LWF4aXMgYmFzZWQgb24gcG92ZXJ0eSBsZXZlbHMgYEVjby4gRGlzYWR2YW50ZWQgKCUpYC4gU2Nob29sLWxldmVsIGRhdGEgZGlzYWdncmVnYXRlZCBieSByYWNlL2V0aG5pY2l0eSBvbiB0aGUgeS1heGlzIHRvIHNob3cgZWFjaCByYWNpYWwvZXRobmljIGdyb3VwcydzIGFjYWRlbWljIHBlcmZvcm1hbmNlIGFzIGBNZWV0cyBHcmFkZSBMZXZlbCAoJSlgLiBJIHdyaXRlIGEgY3VzdG9tIGZ1bmN0aW9uIHRoYXQgdGFrZXMgdGhlIGRhdGEgYW5kIHR3byBmaWx0ZXJpbmcgcGFyYW1ldGVycyB0byBzbGljZSB0aGUgZnVsbCBkYXRhIHNldDogYGNhbXB1cyAlPiUgZmlsdGVyKCdDYW1wdXMgVHlwZScgPT0ge3t0eXBlfX0gJiByZWdpb24gPT0ge3tyfX0pYC4gSSBwYXNzIHRoaXMgZnVuY3Rpb24gdG8gYGxhcHBseSgpYCB0byBnZW5lcmF0ZSBhbnkgb3IgYWxsIGNvbWJpbmF0aW9ucyBvZiBgcmVnaW9uYCBhbmQgYENhbXB1cyBUeXBlYCAodXAgdG8gKQ0KDQpgYGB7ciwgcmVzdWx0cyA9ICJhc2lzIiwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQoNCg0KciA8LSAxOjIwDQoNCnBsb3RzIDwtIGZ1bmN0aW9uKGRhdGEsIHIsIHR5cGUpIHsNCg0KdCA8LSAgZ2dwbG90KGRhdGEgPSBjYW1wdXMgJT4lIGZpbHRlcihgQ2FtcHVzIFR5cGVgID09IHt7dHlwZX19ICYgcmVnaW9uID09IHt7cn19KSwgDQogICAgICAgICBhZXMobGFiZWwgPSBgQ2FtcHVzYCwgbGFiZWwyID0gYFNpemUgb2YgU3R1ZGVudCBHcm91cCBhdCBTY2hvb2xgLCB4ID0gYEVjby4gRGlzYWR2YW50YWdlZCAoJSlgLCB5ID0gYE1lZXRzIEdyYWRlIExldmVsICglKWApKSArDQogIGdlb21fbGluZShhbHBoYSA9IC4zLCBhZXMoZ3JvdXAgPSBjaWQpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAuNCwgYWVzKHNpemUgPSBgU2l6ZSBvZiBTdHVkZW50IEdyb3VwIGF0IFNjaG9vbGAsIGNvbG9yID0gYFN0dWRlbnQgR3JvdXBgKSkgKw0KICBnZW9tX3Ntb290aChzaXplID0gMiwgbWV0aG9kID0gbG9lc3MsIGNvbG9yID0gImJsYWNrIiwgc2UgPSBGQUxTRSwgYWVzKGdyb3VwID0gYFN0dWRlbnQgR3JvdXBgKSkgKw0KICBnZW9tX3Ntb290aChzaXplID0gMS44LCBtZXRob2QgPSBsb2Vzcywgc2UgPSBGQUxTRSwgYWVzKGNvbG9yID0gYFN0dWRlbnQgR3JvdXBgKSkgKw0KICBndWlkZXMoc2l6ZSA9IEZBTFNFKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSArDQogIHhsaW0oYygwLDEwMCkpICsgeWxpbShjKDAsMTAwKSkgKyANCiAgICBnZ3RpdGxlKHBhc3RlKCJSZWdpb24iLCB7e3J9fSwge3t0eXBlfX0sICJQZXJmb3JtYW5jZSBieSBSYWNlL0V0aG5pY2l0eSBhbmQgUG92ZXJ0eSIsIHNlcCA9ICIgIikpDQogIA0KdFtbcl1dIDwtIGdncGxvdGx5KHQsIHRvb2x0aXAgPSBjKCJsYWJlbCIsICJsYWJlbDIiLCAieCIsICJ5IikpDQoNCiNwcmludCh0W1tyXV0pDQojcmV0dXJuKHRbW3JdXSkNCn0NCg0KIyBUaGlzIHdvcmtzIGluIFIgYnV0IG5vdCBmb3IgbWFya2Rvd24gdG8gaHRtbA0KIyBmb3IgKGkgaW4gMToyMCl7DQojICAgcGxvdHMoY2FtcHVzLCBpKQ0KIyB9DQoNCmdnX2hzIDwtIGxhcHBseShyLCBwbG90cywgZGF0YSA9IGNhbXB1cywgdHlwZSA9ICJIaWdoIFNjaG9vbCIpDQoNCmdnX2VzIDwtIGxhcHBseShyLCBwbG90cywgZGF0YSA9IGNhbXB1cywgdHlwZSA9ICJFbGVtZW50YXJ5IFNjaG9vbCIpDQoNCg0KDQpgYGANCg0KDQpgYGB7ciwgcmVzdWx0cyA9ICJhc2lzIiwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQoNCmdnX2VzDQoNCmdnX2hzW1sxXV0NCg0Kc3VicGxvdChnZ19oc1tbMTBdXSwgZ2dfZXNbWzEwXV0sIGdnX2hzW1syMF1dLCBnZ19lc1tbMjBdXSwgbnJvd3MgPSAyKQ0KDQpodG1sdG9vbHM6OnRhZ0xpc3QobGlzdChnZ19oc1tbMTBdXSwgZ2dfZXNbWzEwXV0sIGdnX2hzW1syMF1dLCBnZ19lc1tbMjBdXSkpDQpodG1sdG9vbHM6OnRhZ0xpc3QobGlzdChnZ19oc1tbMV1dLCBnZ19oc1tbM11dLCBnZ19lc1tbMV1dLCBnZ19lc1tbM11dKSkNCg0KYGBgDQoNCg0KIyBQcml2YXRlIFNjaG9vbHMgRGF0YSAyMDE4IC0gMjAxOQ0KDQpgYGB7cn0NCnByaXZhdGUgPC0gIHJlYWRfZXhjZWwoImRvY3MvcHJpdmF0ZSBzY2hvb2xzIDIwMTgtMjAxOS54bHN4IikNCnByaXZhdGUgPC1wcml2YXRlICU+JQ0KICBmaWx0ZXIoIWBHcmFkZSBIaWdoYCAlaW4lIGMoIlByZS1LIiwgIkVhcmx5IEVkdWNhdGlvbiIpICYgQ2xvc2VkID09IEZBTFNFKSAlPiUNCiAgdHJhbnNtdXRlKA0KICAgIGRpc3RyaWN0ID0gYXMubnVtZXJpYyhgRGlzdHJpY3QgTnVtYmVyYCksDQogICAgY291bnR5ID0gYXMubnVtZXJpYyhgQ291bnR5IE51bWJlcmApLA0KICAgIHJlZ2lvbiA9IGFzLm51bWVyaWMoYFJlZ2lvbiBOYW1lYCksDQogICAgZW5yb2xsbWVudF9wcml2YXRlID0gYXMubnVtZXJpYyhFbnJvbGxtZW50KSkNCg0KcHJpdmF0ZV9yZWdpb24gPC0gcHJpdmF0ZSAlPiUNCiAgZ3JvdXBfYnkocmVnaW9uKSAlPiUNCiAgc3VtbWFyaXNlKA0KICAgIHByaXZhdGVfZW5yb2xsbWVudF9yZWdpb24gPSBzdW0oZW5yb2xsbWVudF9wcml2YXRlLCBuYS5ybSA9IFRSVUUpKSAlPiUNCiAgdW5ncm91cCgpDQoNCnB1YmxpY19yZWdpb24gPC0gc2Nob29sICU+JQ0KICBsZWZ0X2pvaW4oY29kZXMsIGJ5ID0gImNpZCIpICU+JQ0KICBncm91cF9ieShyZWdpb24pICU+JQ0KICBzdW1tYXJpc2UoDQogICAgcHVibGljX2Vucm9sbG1lbnRfcmVnaW9uID0gc3VtKGVucm9sbG1lbnRfcHVibGljLCBuYS5ybSA9IFRSVUUpKSAlPiUNCiAgdW5ncm91cCgpDQogIA0KcmVnaW9uIDwtIGxlZnRfam9pbihwcml2YXRlX3JlZ2lvbiwgcHVibGljX3JlZ2lvbiwgYnkgPSAicmVnaW9uIikgDQpyZWdpb24gPC0gcmVnaW9uICU+JQ0KICBtdXRhdGUoDQogICAgYFRvdGFsIEVucm9sbG1lbnRgID0gcHJpdmF0ZV9lbnJvbGxtZW50X3JlZ2lvbiArIHB1YmxpY19lbnJvbGxtZW50X3JlZ2lvbiwNCiAgICBgUHJpdmF0ZSBTdHVkZW50cyBpbiBSZWdpb24gKCUpYCA9IDEwMCAqICggcHJpdmF0ZV9lbnJvbGxtZW50X3JlZ2lvbiAvIGBUb3RhbCBFbnJvbGxtZW50YCksDQogICAgUmVnaW9uX2xhYiA9IHBhc3RlKCJSZWdpb24iLCByZWdpb24sIHNlcCA9ICIgIiksDQogICAgUmVnaW9uID0gZmFjdG9yKHJlZ2lvbiwgb3JkZXJlZCA9IFRSVUUsIGxhYmVscyA9IFJlZ2lvbl9sYWIpKSAlPiUNCiAgYXJyYW5nZShkZXNjKGBQcml2YXRlIFN0dWRlbnRzIGluIFJlZ2lvbiAoJSlgKSkgJT4lDQogIG11dGF0ZSgNCiAgICBSZWdpb25fbGFiID0gcGFzdGUoIlJlZ2lvbiIsIHJlZ2lvbiwgc2VwID0gIiAiKSwNCiAgICBgUmVnaW9uIE9yZGVyZWRgID0gZmFjdG9yKHJlZ2lvbiwgbGV2ZWxzID0gcmVnaW9uLCBsYWJlbHMgPSBwYXN0ZSgiUmVnaW9uIiwgcmVnaW9uLCBzZXAgPSAiICIpKSkNCg0KI3JtKHByaXZhdGUsIHByaXZhdGVfcmVnaW9uLCBwdWJsaWNfcmVnaW9uKQ0KDQpnZ3Bsb3RseShnZ3Bsb3QocmVnaW9uLCBhZXMoeCA9IGxvZyhgVG90YWwgRW5yb2xsbWVudGApLCB5ID0gYFByaXZhdGUgU3R1ZGVudHMgaW4gUmVnaW9uICglKWAsIGxhYmVsID0gcmVnaW9uKSkgKw0KICBnZW9tX3Ntb290aChhbHBoYSA9IC40LCBtZXRob2QgPSBsbSkgKw0KICAgIGdlb21fdGV4dChzaXplID0gNSkpDQpnZ3Bsb3RseShnZ3Bsb3QocmVnaW9uICU+JSBmaWx0ZXIoIXJlZ2lvbiAlaW4lIGMoMSwgMykpLCBhZXMoeCA9IGxvZyhgVG90YWwgRW5yb2xsbWVudGApLCB5ID0gYFByaXZhdGUgU3R1ZGVudHMgaW4gUmVnaW9uICglKWAsIGxhYmVsID0gcmVnaW9uKSkgKw0KICBnZW9tX3Ntb290aChhbHBoYSA9IC40LCBtZXRob2QgPSBsbSkgKw0KICAgIGdlb21fdGV4dChzaXplID0gNSkpDQoNCnAxIDwtIGdncGxvdGx5KGdncGxvdChyZWdpb24sIGFlcyh4ID0gYFByaXZhdGUgU3R1ZGVudHMgaW4gUmVnaW9uICglKWAsIHkgPSBSZWdpb24pKSArDQogIGdlb21fY29sKCkpDQoNCnAyIDwtIGdncGxvdGx5KGdncGxvdChyZWdpb24sIGFlcyh4ID0gYFByaXZhdGUgU3R1ZGVudHMgaW4gUmVnaW9uICglKWAsIHkgPSBgUmVnaW9uIE9yZGVyZWRgKSkgKw0KICBnZW9tX2NvbCgpKQ0KDQpzdWJwbG90KHAxLCBwMiwgbWFyZ2luID0gMC4wNykNCg0KYGBgDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQo=